#include <iostream>
#include <algorithm>
#include <thread>
#include <string>
#include <exception>
#include <cuda.h>
#include "NvDecoder/NvDecoder.h"
#include "../Utils/NvCodecUtils.h"
#include "../Utils/ColorSpace.h"
#include "../Utils/FFmpegDemuxer.h"
#include "../Common/AppDecUtils.h"
#include "unreDec_api.h"
simplelogger::Logger* logger = simplelogger::LoggerFactory::CreateConsoleLogger();

void ConvertSemiplanarToPlanar(uint8_t *pHostFrame, int nWidth, int nHeight, int nBitDepth) {
    if (nBitDepth == 8) {
        // nv12->iyuv
        YuvConverter<uint8_t> converter8(nWidth, nHeight);
        converter8.UVInterleavedToPlanar(pHostFrame);
    } else {
        // p016->yuv420p16
        YuvConverter<uint16_t> converter16(nWidth, nHeight);
        converter16.UVInterleavedToPlanar((uint16_t *)pHostFrame);
    }
}

struct MemBlock2Write
{
    std::string outFilePath_;
    long long lmt_;
    int thisWriteBlockIdx_;
    char* mem_;
    char* memFloat_;
    long long floatFlag_;
    int frameCnt_;
    bool withTimestamp_;
    int timestampOffset_;
    MemBlock2Write(const std::string& outFilePath, const int& lmtMB,const bool&withTimestamp=false)
    {
        outFilePath_ = outFilePath;
        lmt_ = 1024ll * 1024ll * lmtMB;
        thisWriteBlockIdx_ = 0;
        floatFlag_ = 0;
        mem_ = new char[lmt_];
        memFloat_ = mem_;
        frameCnt_ = 0;
        withTimestamp_=withTimestamp;
        timestampOffset_=0;
    }
    int push(const char*data,const int&size,const int64_t&thisTimeStamp=0)
    {
        if(withTimestamp_&&timestampOffset_==0)
        {
            int maxFrameCnt = lmt_/(size+8);
            timestampOffset_ = maxFrameCnt*8;
            memFloat_=memFloat_+timestampOffset_;
            floatFlag_=floatFlag_+timestampOffset_;
        }
        if(withTimestamp_)((int64_t*)mem_)[frameCnt_] = thisTimeStamp;
        if (floatFlag_+ size< lmt_)
        {
            memcpy(memFloat_, data, size);
            floatFlag_ += size;
            memFloat_ += size;
            frameCnt_++;
        }
        else
        {
            TIMESTART;
            std::string outPathStr = outFilePath_ + ".._" + std::to_string(thisWriteBlockIdx_);
            std::ofstream fpOut(outPathStr, std::ios::out | std::ios::binary);
            if (!fpOut)
            {
                std::ostringstream err;
                err << "Unable to open output file: " << outPathStr << std::endl;
                throw std::invalid_argument(err.str());
            }
            fpOut.write(mem_, floatFlag_);
            fpOut.close();
            TIMEEND("File write : ");
            floatFlag_ = 0;//**
            return MEM_SPACE_FULL;//**
            thisWriteBlockIdx_++;
            memFloat_ = mem_;
            memcpy(memFloat_, data, size);
            floatFlag_ = size;
            memFloat_ += size;
        }
        return OK;
    }
    ~MemBlock2Write()
    {
        if (floatFlag_ != 0)
        {
            TIMESTART;
            std::string outPathStr = outFilePath_ + ".._" + std::to_string(thisWriteBlockIdx_);
            std::ofstream fpOut(outPathStr, std::ios::out | std::ios::binary);
            if (!fpOut)
            {
                std::ostringstream err;
                err << "Unable to open output file: " << outPathStr << std::endl;
                throw std::invalid_argument(err.str());
            }
            fpOut.write(mem_, floatFlag_);
            fpOut.close();
            TIMEEND("File write : ");
        }
        if (mem_)
        {
            delete[]mem_;
        }
        return;
    }
};
struct MemBlock
{
    std::string outFilePath_;
    long long lmt_;
    char* mem_;
    char* memFloat_;
    long long floatFlag_;
    int frameCnt_;
    bool withTimestamp_;
    int timestampOffset_;
    MemBlock(char* mem, const int& lmtMB,const bool&withTimestamp=false)
    {
        lmt_ = 1024ll * 1024ll * lmtMB;
        floatFlag_ = 0;
        mem_ = mem;
        memFloat_ = mem_;
        frameCnt_ = 0;
        withTimestamp_=withTimestamp;
        timestampOffset_=0;
    }
    int push(const char* data, const int& size, const int64_t&thisTimeStamp=0)
    {
        if(withTimestamp_&&timestampOffset_==0)
        {
            int maxFrameCnt = lmt_/(size+8);
            timestampOffset_ = maxFrameCnt*8;
            memFloat_=memFloat_+timestampOffset_;
            floatFlag_=floatFlag_+timestampOffset_;
        }
        if(withTimestamp_)((int64_t*)mem_)[frameCnt_] = thisTimeStamp;
        if (floatFlag_ + size < lmt_)
        {
            //std::cout<<(int)data <<std::endl;
            memcpy(memFloat_, data, size);
            floatFlag_ += size;
            memFloat_ += size;
            frameCnt_++;
            return OK;
        }
        else
        {
            return MEM_SPACE_FULL;
        }
    }
};
void GetImage(CUdeviceptr dpSrc, uint8_t* pDst, int nWidth, int nHeight)
{
    CUDA_MEMCPY2D m = { 0 };
    m.WidthInBytes = nWidth;
    m.Height = nHeight;
    m.srcMemoryType = CU_MEMORYTYPE_DEVICE;
    m.srcDevice = (CUdeviceptr)dpSrc;
    m.srcPitch = m.WidthInBytes;
    m.dstMemoryType = CU_MEMORYTYPE_HOST;
    m.dstDevice = (CUdeviceptr)(m.dstHost = pDst);
    m.dstPitch = m.WidthInBytes;
    cuMemcpy2D(&m);
}
struct MyException1 : public std::exception
{
    const char* what() const throw ()
    {
        return "C++ Exception";
    }
};
struct MyException2 : public std::exception
{
    const char* what() const throw ()
    {
        return "C++ Exception";
    }
};





class CentextSet
{
private:
    CentextSet() {};
    CentextSet(const CentextSet& other) {};
public:
    static CentextSet* GetInstance()
    {
        static CentextSet instance;
        return &instance;
    }
    static CentextSet* instance_;
    static std::vector<CUcontext>cuContextGlobals;

};
CentextSet* CentextSet::instance_ = NULL;
std::vector<CUcontext> CentextSet::cuContextGlobals = std::vector<CUcontext>(128, NULL);
CUcontext cuContextGlobal = NULL;
int DecoderInit(int iGpu)
{
    //CentextSet* ins = CentextSet::GetInstance();    auto asd = ins->cuContextGlobals;
    if (iGpu<0 || iGpu>= CentextSet::GetInstance()->cuContextGlobals.size())
    {
        return GPU_IDX_ERR;
    }
    if (CentextSet::GetInstance()->cuContextGlobals[iGpu] != NULL)
    {
        return GPU_NOT_INIT;
    }
    try
    {
        if (cuInit(0) < 0) throw MyException1();
        int nGpu = 0;
        if (cuDeviceGetCount(&nGpu) < 0)throw MyException2();
        if (iGpu < 0 || iGpu >= nGpu) {
            std::cout << "GPU ordinal out of range. Should be within [" << 0 << ", " << nGpu - 1 << "]" << std::endl;
            return  GPU_OUT_RANGE;
        }
        createCudaContext(&(CentextSet::GetInstance()->cuContextGlobals[iGpu]), iGpu, 0);
    }
    catch (const MyException1& e)
    {
        return GPU_INIT_ERR;
    }
    catch (const MyException2& e)
    {
        return GPU_GETDEVICE_ERR;
    }
    catch (...)
    {
        return GPU_ANOTHER_ERR;
    }
    return OK;
}

int DecoderFile(int gpuIdx,char* fileIn, char* fileOut, struct decoderParam* pStruct)
{
    if (gpuIdx < 0 || gpuIdx >= CentextSet::GetInstance()->cuContextGlobals.size())
    {
        return GPU_OUT_RANGE;
    }
    if (CentextSet::GetInstance()->cuContextGlobals[gpuIdx]==NULL)
    {
        return GPU_INIT_ERR;
    }
    //CUcontext&cuContextGlobal = CentextSet::GetInstance()->cuContextGlobals[gpuIdx];
    try
    {
        CheckInputFile(fileIn);
    }
    catch (...)
    {
        return FILE_ERR;
    }
    int cropRight = pStruct->cropRight;
    int cropLeft = pStruct->cropLeft;
    int cropBottom = pStruct->cropBottom;
    int cropTop = pStruct->cropTop;
    int resizeH = pStruct->resizeH;
    int resizeW = pStruct->resizeW;
    Rect cropRect = {};
    Dim resizeDim = {};
    if (cropRight <= 0 && cropLeft <= 0 && cropBottom <= 0 && cropTop <= 0) {}
    else
    {
        if ((cropRight - cropLeft) % 2 == 1 || (cropBottom - cropTop) % 2 == 1) return PARAM_CROP_ERR;
        cropRect.b = cropBottom;
        cropRect.t = cropTop;
        cropRect.l = cropLeft;
        cropRect.r = cropRight;
    }
    if (resizeH <= 0 && resizeW <= 0) {}
    else
    {
        if (resizeH % 2 == 1 || resizeW % 2 == 1) return PARAM_RESIZE_ERR;
        resizeDim.h = resizeH;
        resizeDim.w = resizeW;
    }
    int decRet = DEC_UNSUPPORT_FRAME;
    try
    {
        bool useDeviceFrame = false;
        CUdeviceptr pTmpImage = 0;
        std::vector<uint8_t>pImage;
        if (pStruct->rawDataType > 0)
        {
            useDeviceFrame = true;
        }
        FFmpegDemuxer demuxer(fileIn,(double)pStruct->startTime*0.001);
        NvDecoder dec(CentextSet::GetInstance()->cuContextGlobals[gpuIdx], useDeviceFrame, FFmpeg2NvCodecId(demuxer.GetVideoCodec()), false, false, &cropRect, &resizeDim);
        int nVideoBytes = 0, nFrameReturned = 0, nFrame = 0;
        uint8_t* pVideo = NULL, * pFrame;
        bool bDecodeOutSemiPlanar = false;
        int jump = pStruct->jump;
        MemBlock2Write mem(fileOut, pStruct->lmtMB, pStruct->memWithTimestamp);
        pStruct->retDecodeWidth = demuxer.GetWidth();
        pStruct->retDecodeHeigth = demuxer.GetHeight();
        pStruct->retFrameSize = demuxer.GetFrameSize();
        if(! (pStruct->retDecodeWidth > 0 && pStruct->retDecodeHeigth > 0 && pStruct->retFrameSize > 0))
        {
            return DEC_HEIGHTWDITH_ERR;
        }
        if (pStruct->rawDataType > 0)
        {
            pStruct->retFrameSize *= 2;
            pImage.resize(pStruct->retFrameSize);
            cuMemAlloc(&pTmpImage, pStruct->retFrameSize);
        }
        int iMatrix = 0;
        unsigned long long totalInt = 0;
        std::cout << "\tjump = " << jump << std::endl;
        std::cout << "\tpklLmt = " << pStruct->lmtMB << std::endl;
        pStruct->retEndTime = -1;
        int64_t endTime = 0;
        TIMESTART;
        do {
            demuxer.Demux(&pVideo, &nVideoBytes,&endTime);
            nFrameReturned = dec.Decode(pVideo, nVideoBytes);
            if (dec.GetOutputFormat() != cudaVideoSurfaceFormat_NV12)
            {
                continue;
            }
            else
            {
                decRet &= OK;
            }
            if (endTime < pStruct->startTime)continue;
            int pushRet = OK;
            for (int i = 0; i < nFrameReturned; i++) {
                if (dec.GetBitDepth() != 8)
                {
                    return DEC_DEPTH_NOT_8;
                }
                pFrame = dec.GetFrame();
                iMatrix = dec.GetVideoFormatInfo().video_signal_description.matrix_coefficients;
                totalInt++;
                if (totalInt % jump == 0)
                {
                    //int FrameSize = dec.GetFrameSize();
                    
                    {
                        switch (pStruct->rawDataType) {
                        case 0:
                            pushRet = mem.push(reinterpret_cast<char*>(pFrame), pStruct->retFrameSize,endTime);
                            break;
                        case 1:
                            Nv12ToColorPlanar<BGRA32>(pFrame, dec.GetWidth(), (uint8_t*)pTmpImage, dec.GetWidth(), dec.GetWidth(), dec.GetHeight(), iMatrix);
                            GetImage(pTmpImage, &pImage[0], dec.GetWidth(), 3 * dec.GetHeight());
                            pushRet = mem.push(reinterpret_cast<char*>(&pImage[0]), pStruct->retFrameSize,endTime);
                            break;
                        case 2:
                            Nv12ToColorPlanar<RGBA32>(pFrame, dec.GetWidth(), (uint8_t*)pTmpImage, dec.GetWidth(), dec.GetWidth(), dec.GetHeight(), iMatrix);
                            GetImage(pTmpImage, &pImage[0], dec.GetWidth(), 3 * dec.GetHeight());
                            pushRet = mem.push(reinterpret_cast<char*>(&pImage[0]), pStruct->retFrameSize,endTime);
                            break;
                        case 3:
                            Nv12ToColorPlanar_HWC<BGRA32>(pFrame, dec.GetWidth(), (uint8_t*)pTmpImage, dec.GetWidth(), dec.GetWidth(), dec.GetHeight(), iMatrix);
                            GetImage(pTmpImage, &pImage[0], dec.GetWidth(), 3 * dec.GetHeight());
                            pushRet = mem.push(reinterpret_cast<char*>(&pImage[0]), pStruct->retFrameSize,endTime);
                            break;
                        case 4:
                            Nv12ToColorPlanar_HWC<RGBA32>(pFrame, dec.GetWidth(), (uint8_t*)pTmpImage, dec.GetWidth(), dec.GetWidth(), dec.GetHeight(), iMatrix);
                            GetImage(pTmpImage, &pImage[0], dec.GetWidth(), 3 * dec.GetHeight());
                            pushRet = mem.push(reinterpret_cast<char*>(&pImage[0]), pStruct->retFrameSize,endTime);
                            break;
                        default:return DEC_UNSUPPORT_RAWDATATYPE;
                        }
                    }
                    if (pushRet != OK)
                    {
                        break;
                    }
                }
            }
            if (pushRet != OK)
            {
                pStruct->retEndTime = endTime;
                break;
            }
            nFrame += nFrameReturned;
        } while (nVideoBytes);
        pStruct->retMemFrameNum = mem.frameCnt_;
        pStruct->retMemTimestampOffset= mem.timestampOffset_;
        TIMEEND("decode loop : ");
        //std::vector <std::string> aszDecodeOutFormat = { "NV12", "P016", "YUV444", "YUV444P16" };
        //aszDecodeOutFormat[0] = "iyuv";   aszDecodeOutFormat[1] = "yuv420p16";
        //std::cout << "Total frame decoded: " << nFrame << std::endl << aszDecodeOutFormat[dec.GetOutputFormat()] << " format" << std::endl;

    }
    catch (...)
    {
        return DEC_ANOTHER_ERR;
    }
    return decRet;
}
int DecoderMem(int gpuIdx, char* fileIn, char* mem, struct decoderParam* pStruct)
{
    if (gpuIdx < 0 || gpuIdx >= CentextSet::GetInstance()->cuContextGlobals.size())
    {
        return GPU_OUT_RANGE;
    }
    if (CentextSet::GetInstance()->cuContextGlobals[gpuIdx] == NULL)
    {
        return GPU_INIT_ERR;
    }
    //CUcontext& cuContextGlobal = CentextSet::GetInstance()->cuContextGlobals[gpuIdx];
    try
    {
        CheckInputFile(fileIn);
    }
    catch (...)
    {
        return FILE_ERR;
    }
    int cropRight = pStruct->cropRight;
    int cropLeft = pStruct->cropLeft;
    int cropBottom = pStruct->cropBottom;
    int cropTop = pStruct->cropTop;
    int resizeH = pStruct->resizeH;
    int resizeW = pStruct->resizeW;
    Rect cropRect = {};
    Dim resizeDim = {};
    if (cropRight <= 0 && cropLeft <= 0 && cropBottom <= 0 && cropTop <= 0) {}
    else
    {
        if ((cropRight - cropLeft) % 2 == 1 || (cropBottom - cropTop) % 2 == 1) return PARAM_CROP_ERR;
        cropRect.b = cropBottom;
        cropRect.t = cropTop;
        cropRect.l = cropLeft;
        cropRect.r = cropRight;
    }
    if (resizeH <= 0 && resizeW <= 0) {}
    else
    {
        if (resizeH % 2 == 1 || resizeW % 2 == 1) return PARAM_RESIZE_ERR;
        resizeDim.h = resizeH;
        resizeDim.w = resizeW;
    }
    int decRet = DEC_UNSUPPORT_FRAME;
    try
    {
        bool useDeviceFrame = false;
        CUdeviceptr pTmpImage = 0;
        std::vector<uint8_t>pImage;
        if (pStruct->rawDataType > 0)
        {
            useDeviceFrame = true;
        }
        FFmpegDemuxer demuxer(fileIn, (double)pStruct->startTime * 0.001);
        NvDecoder dec(CentextSet::GetInstance()->cuContextGlobals[gpuIdx], useDeviceFrame, FFmpeg2NvCodecId(demuxer.GetVideoCodec()), false, false, &cropRect, &resizeDim);
        int nVideoBytes = 0, nFrameReturned = 0, nFrame = 0;
        uint8_t* pVideo = NULL, * pFrame;
        bool bDecodeOutSemiPlanar = false;
        int jump = pStruct->jump;
        MemBlock thisMemBlock(mem, pStruct->lmtMB, pStruct->memWithTimestamp);
        pStruct->retDecodeWidth = demuxer.GetWidth();
        pStruct->retDecodeHeigth = demuxer.GetHeight();
        pStruct->retFrameSize = demuxer.GetFrameSize();
        if (!(pStruct->retDecodeWidth > 0 && pStruct->retDecodeHeigth > 0 && pStruct->retFrameSize > 0))
        {
            return DEC_HEIGHTWDITH_ERR;
        }
        if (pStruct->rawDataType > 0)
        {
            pStruct->retFrameSize *= 2;
            pImage.resize(pStruct->retFrameSize);
            cuMemAlloc(&pTmpImage, pStruct->retFrameSize);
        }
        int iMatrix = 0;
        unsigned long long totalInt = 0;
        std::cout << "\tjump = " << jump << std::endl;
        std::cout << "\tpklLmt = " << pStruct->lmtMB << std::endl;
        pStruct->retEndTime = -1;
        int64_t endTime = 0;
        TIMESTART;
        do {
            demuxer.Demux(&pVideo, &nVideoBytes, &endTime);
            nFrameReturned = dec.Decode(pVideo, nVideoBytes);
            if (dec.GetOutputFormat() != cudaVideoSurfaceFormat_NV12 || nFrameReturned==0)
            {
                continue;
            }
            else
            {
                decRet &= OK;
            }
            if (endTime < pStruct->startTime)continue;
            //std::cout << endTime << std::endl;
            int pushRet = OK;
            for (int i = 0; i < nFrameReturned; i++) {
                if (dec.GetBitDepth() != 8)
                {
                    return DEC_DEPTH_NOT_8;
                }
                pFrame = dec.GetFrame();
                iMatrix = dec.GetVideoFormatInfo().video_signal_description.matrix_coefficients;
                totalInt++;
                if (totalInt % jump == 0)
                {
                    switch (pStruct->rawDataType)
                    {
                    case 0:
                        pushRet = thisMemBlock.push(reinterpret_cast<char*>(pFrame), pStruct->retFrameSize);
                        break;
                    case 1:
                        Nv12ToColorPlanar<BGRA32>(pFrame, dec.GetWidth(), (uint8_t*)pTmpImage, dec.GetWidth(), dec.GetWidth(), dec.GetHeight(), iMatrix);
                        GetImage(pTmpImage, &pImage[0], dec.GetWidth(), 3 * dec.GetHeight());
                        pushRet = thisMemBlock.push(reinterpret_cast<char*>(&pImage[0]), pStruct->retFrameSize,endTime);
                        break;
                    case 2:
                        Nv12ToColorPlanar<RGBA32>(pFrame, dec.GetWidth(), (uint8_t*)pTmpImage, dec.GetWidth(), dec.GetWidth(), dec.GetHeight(), iMatrix);
                        GetImage(pTmpImage, &pImage[0], dec.GetWidth(), 3 * dec.GetHeight());
                        pushRet = thisMemBlock.push(reinterpret_cast<char*>(&pImage[0]), pStruct->retFrameSize,endTime);
                        break;
                    case 3:
                        Nv12ToColorPlanar_HWC<BGRA32>(pFrame, dec.GetWidth(), (uint8_t*)pTmpImage, dec.GetWidth(), dec.GetWidth(), dec.GetHeight(), iMatrix);
                        GetImage(pTmpImage, &pImage[0], dec.GetWidth(), 3 * dec.GetHeight());
                        pushRet = thisMemBlock.push((char*)&pImage[0], pStruct->retFrameSize,endTime);
                        break;
                    case 4:
                        Nv12ToColorPlanar_HWC<RGBA32>(pFrame, dec.GetWidth(), (uint8_t*)pTmpImage, dec.GetWidth(), dec.GetWidth(), dec.GetHeight(), iMatrix);
                        GetImage(pTmpImage, &pImage[0], dec.GetWidth(), 3 * dec.GetHeight());
                        pushRet = thisMemBlock.push(reinterpret_cast<char*>(&pImage[0]), pStruct->retFrameSize,endTime);
                        break;
                    default:return DEC_UNSUPPORT_RAWDATATYPE;
                    }
                    if (pushRet!=OK)
                    {
                        break;
                    }
                }
            }
            if (pushRet != OK)
            {
                pStruct->retEndTime = endTime;
                break;
            }
            nFrame += nFrameReturned;
        } while (nVideoBytes);
        pStruct->retMemFrameNum = thisMemBlock.frameCnt_;
        pStruct->retMemTimestampOffset= thisMemBlock.timestampOffset_;
        TIMEEND("decode loop : ");
        //std::vector <std::string> aszDecodeOutFormat = { "NV12", "P016", "YUV444", "YUV444P16" };
        //aszDecodeOutFormat[0] = "iyuv";   aszDecodeOutFormat[1] = "yuv420p16";
        //std::cout << "Total frame decoded: " << nFrame << std::endl << aszDecodeOutFormat[dec.GetOutputFormat()] << " format" << std::endl;
    }
    catch (...)
    {
        return DEC_ANOTHER_ERR;
    }
    return decRet;
}

int DecoderDestroy()
{
    //cuContextGlobal= NULL;
    for (auto&d: CentextSet::GetInstance()->cuContextGlobals)
    {
        d = NULL;
    }
    return OK;
}


class DecSet
{
public:
    CUcontext ctx;
    FFmpegDemuxer *demuxer;
    NvDecoder *dec;
    DecSet()
        : ctx(NULL)
        , demuxer(NULL)
        , dec(NULL)
    {

    }

    DecSet(const DecSet& src)
        : ctx(src.ctx)
        , demuxer(src.demuxer)
        , dec(src.dec)
    {

    }

};

std::vector<DecSet> decSetGlobals(128, DecSet());

int DecoderInit2(int iGpu)
{
    //CentextSet* ins = CentextSet::GetInstance();    auto asd = ins->cuContextGlobals;
    if (iGpu<0 || iGpu >= decSetGlobals.size())
    {
        return GPU_IDX_ERR;
    }
    if (decSetGlobals[iGpu].ctx != NULL)
    {
        return GPU_NOT_INIT;
    }
    try
    {
        if (cuInit(0) < 0) throw MyException1();
        int nGpu = 0;
        if (cuDeviceGetCount(&nGpu) < 0)throw MyException2();
        if (iGpu < 0 || iGpu >= nGpu) {
            std::cout << "GPU ordinal out of range. Should be within [" << 0 << ", " << nGpu - 1 << "]" << std::endl;
            return  GPU_OUT_RANGE;
        }
        createCudaContext(&(decSetGlobals[iGpu].ctx), iGpu, 0);
    }
    catch (const MyException1& e)
    {
        return GPU_INIT_ERR;
    }
    catch (const MyException2& e)
    {
        return GPU_GETDEVICE_ERR;
    }
    catch (...)
    {
        return GPU_ANOTHER_ERR;
    }
    return OK;
}


int DecoderDestroy2()
{
    //cuContextGlobal= NULL;
    for (auto&d: decSetGlobals)
    {
        d.ctx = NULL;
    }
    return OK;
}

int Open2(int gpuIdx, char* fileIn, struct decoderParam* pStruct)
{
    if (gpuIdx < 0 || gpuIdx >= decSetGlobals.size())
    {
        return GPU_OUT_RANGE;
    }
    if (decSetGlobals[gpuIdx].ctx == NULL)
    {
        return GPU_INIT_ERR;
    }
    //CUcontext& cuContextGlobal = CentextSet::GetInstance()->cuContextGlobals[gpuIdx];
    try
    {
        CheckInputFile(fileIn);
    }
    catch (...)
    {
        return FILE_ERR;
    }
    int cropRight = pStruct->cropRight;
    int cropLeft = pStruct->cropLeft;
    int cropBottom = pStruct->cropBottom;
    int cropTop = pStruct->cropTop;
    int resizeH = pStruct->resizeH;
    int resizeW = pStruct->resizeW;
    Rect cropRect = {};
    Dim resizeDim = {};
    if (cropRight <= 0 && cropLeft <= 0 && cropBottom <= 0 && cropTop <= 0) {}
    else
    {
        if ((cropRight - cropLeft) % 2 == 1 || (cropBottom - cropTop) % 2 == 1) return PARAM_CROP_ERR;
        cropRect.b = cropBottom;
        cropRect.t = cropTop;
        cropRect.l = cropLeft;
        cropRect.r = cropRight;
    }
    if (resizeH <= 0 && resizeW <= 0) {}
    else
    {
        if (resizeH % 2 == 1 || resizeW % 2 == 1) return PARAM_RESIZE_ERR;
        resizeDim.h = resizeH;
        resizeDim.w = resizeW;
    }
    int decRet = DEC_UNSUPPORT_FRAME;
    try
    {
        bool useDeviceFrame = false;

        if (pStruct->rawDataType > 0)
        {
            useDeviceFrame = true;
        }

        decSetGlobals[gpuIdx].demuxer=new FFmpegDemuxer(fileIn, (double)pStruct->startTime * 0.001);
        decSetGlobals[gpuIdx].dec=new NvDecoder(decSetGlobals[gpuIdx].ctx, useDeviceFrame, FFmpeg2NvCodecId(decSetGlobals[gpuIdx].demuxer->GetVideoCodec()), false, false, &cropRect, &resizeDim);
    }
    catch (...)
    {
        return DEC_ANOTHER_ERR;
    }


    return decRet;

}

int DecoderMem3(int gpuIdx, char* mem, struct decoderParam* pStruct)
{
    int decRet = DEC_UNSUPPORT_FRAME;
    try
    {
        CUdeviceptr pTmpImage = 0;
        std::vector<uint8_t>pImage;

        int nVideoBytes = 0, nFrameReturned = 0, nFrame = 0;
        uint8_t* pVideo = NULL, * pFrame;
        bool bDecodeOutSemiPlanar = false;
        int jump = pStruct->jump;
        MemBlock thisMemBlock(mem, pStruct->lmtMB, pStruct->memWithTimestamp);
        pStruct->retDecodeWidth = decSetGlobals[gpuIdx].demuxer->GetWidth();
        pStruct->retDecodeHeigth = decSetGlobals[gpuIdx].demuxer->GetHeight();
        pStruct->retFrameSize = decSetGlobals[gpuIdx].demuxer->GetFrameSize();
        if (!(pStruct->retDecodeWidth > 0 && pStruct->retDecodeHeigth > 0 && pStruct->retFrameSize > 0))
        {
            return DEC_HEIGHTWDITH_ERR;
        }
        if (pStruct->rawDataType > 0)
        {
            pStruct->retFrameSize *= 2;
            pImage.resize(pStruct->retFrameSize);
            cuMemAlloc(&pTmpImage, pStruct->retFrameSize);
        }
        int iMatrix = 0;
        unsigned long long totalInt = 0;
//        std::cout << "\tjump = " << jump << std::endl;
//        std::cout << "\tpklLmt = " << pStruct->lmtMB << std::endl;
        pStruct->retEndTime = -1;
        int64_t endTime = 0;
        TIMESTART;
        do {
            decSetGlobals[gpuIdx].demuxer->Demux(&pVideo, &nVideoBytes, &endTime);
            nFrameReturned = decSetGlobals[gpuIdx].dec->Decode(pVideo, nVideoBytes);
            if (decSetGlobals[gpuIdx].dec->GetOutputFormat() != cudaVideoSurfaceFormat_NV12 || nFrameReturned==0)
            {
//             printf("nFrameReturned continue\n");
                continue;
            }
            else
            {
                decRet &= OK;
            }
//            printf("endTime=%d, nFrameReturned=%d, nVideoBytes=%d\n", endTime, nFrameReturned, nVideoBytes);
            if (endTime < pStruct->startTime){
//                printf("endTime continue\n");
                continue;
            }

            int pushRet = OK;
            for (int i = 0; i < nFrameReturned; i++) {
                if (decSetGlobals[gpuIdx].dec->GetBitDepth() != 8)
                {
                    return DEC_DEPTH_NOT_8;
                }
                pFrame = decSetGlobals[gpuIdx].dec->GetFrame();
                iMatrix = decSetGlobals[gpuIdx].dec->GetVideoFormatInfo().video_signal_description.matrix_coefficients;
                totalInt++;
                if (totalInt % jump == 0)
                {
                    switch (pStruct->rawDataType)
                    {
                    case 0:
                        pushRet = thisMemBlock.push(reinterpret_cast<char*>(pFrame), pStruct->retFrameSize,endTime);
                        break;
                    case 1:
                        Nv12ToColorPlanar<BGRA32>(pFrame, decSetGlobals[gpuIdx].dec->GetWidth(), (uint8_t*)pTmpImage, decSetGlobals[gpuIdx].dec->GetWidth(), decSetGlobals[gpuIdx].dec->GetWidth(), decSetGlobals[gpuIdx].dec->GetHeight(), iMatrix);
                        GetImage(pTmpImage, &pImage[0], decSetGlobals[gpuIdx].dec->GetWidth(), 3 * decSetGlobals[gpuIdx].dec->GetHeight());
                        pushRet = thisMemBlock.push(reinterpret_cast<char*>(&pImage[0]), pStruct->retFrameSize,endTime);
                        break;
                    case 2:
                        Nv12ToColorPlanar<RGBA32>(pFrame, decSetGlobals[gpuIdx].dec->GetWidth(), (uint8_t*)pTmpImage, decSetGlobals[gpuIdx].dec->GetWidth(), decSetGlobals[gpuIdx].dec->GetWidth(), decSetGlobals[gpuIdx].dec->GetHeight(), iMatrix);
                        GetImage(pTmpImage, &pImage[0], decSetGlobals[gpuIdx].dec->GetWidth(), 3 * decSetGlobals[gpuIdx].dec->GetHeight());
                        pushRet = thisMemBlock.push(reinterpret_cast<char*>(&pImage[0]), pStruct->retFrameSize,endTime);
                        break;
                    case 3:
                        Nv12ToColorPlanar_HWC<BGRA32>(pFrame, decSetGlobals[gpuIdx].dec->GetWidth(), (uint8_t*)pTmpImage, decSetGlobals[gpuIdx].dec->GetWidth(), decSetGlobals[gpuIdx].dec->GetWidth(), decSetGlobals[gpuIdx].dec->GetHeight(), iMatrix);
                        GetImage(pTmpImage, &pImage[0], decSetGlobals[gpuIdx].dec->GetWidth(), 3 * decSetGlobals[gpuIdx].dec->GetHeight());
                        pushRet = thisMemBlock.push((char*)&pImage[0], pStruct->retFrameSize,endTime);
                        break;
                    case 4:
                        Nv12ToColorPlanar_HWC<RGBA32>(pFrame, decSetGlobals[gpuIdx].dec->GetWidth(), (uint8_t*)pTmpImage, decSetGlobals[gpuIdx].dec->GetWidth(), decSetGlobals[gpuIdx].dec->GetWidth(), decSetGlobals[gpuIdx].dec->GetHeight(), iMatrix);
                        GetImage(pTmpImage, &pImage[0], decSetGlobals[gpuIdx].dec->GetWidth(), 3 * decSetGlobals[gpuIdx].dec->GetHeight());
                        pushRet = thisMemBlock.push(reinterpret_cast<char*>(&pImage[0]), pStruct->retFrameSize,endTime);
                        break;
                    default:return DEC_UNSUPPORT_RAWDATATYPE;
                    }
                    if (pushRet!=OK)
                    {
                        break;
                    }
                }
            }
            if (pushRet != OK)
            {
                pStruct->retEndTime = endTime;
                break;
            }
            nFrame += nFrameReturned;
        } while (nVideoBytes);
        pStruct->retMemFrameNum = thisMemBlock.frameCnt_;
        pStruct->retMemTimestampOffset= thisMemBlock.timestampOffset_;
        TIMEEND("decode loop : ");
        //std::vector <std::string> aszDecodeOutFormat = { "NV12", "P016", "YUV444", "YUV444P16" };
        //aszDecodeOutFormat[0] = "iyuv";   aszDecodeOutFormat[1] = "yuv420p16";
        //std::cout << "Total frame decoded: " << nFrame << std::endl << aszDecodeOutFormat[decSetGlobals[gpuIdx].dec->GetOutputFormat()] << " format" << std::endl;
    }
    catch (...)
    {
        return DEC_ANOTHER_ERR;
    }


    return decRet;

}





int DecoderMem2(int gpuIdx, char* fileIn, char* mem, struct decoderParam* pStruct)
{

#if 1
    int ret1=Open2(gpuIdx, fileIn, pStruct);
    if(ret1==DEC_UNSUPPORT_FRAME)
        ret1= DecoderMem3(gpuIdx, mem, pStruct);
    return ret1;
#else
    if (gpuIdx < 0 || gpuIdx >= decSetGlobals.size())
    {
        return GPU_OUT_RANGE;
    }
    if (decSetGlobals[gpuIdx].ctx == NULL)
    {
        return GPU_INIT_ERR;
    }
    //CUcontext& cuContextGlobal = CentextSet::GetInstance()->cuContextGlobals[gpuIdx];
    try
    {
        CheckInputFile(fileIn);
    }
    catch (...)
    {
        return FILE_ERR;
    }
    int cropRight = pStruct->cropRight;
    int cropLeft = pStruct->cropLeft;
    int cropBottom = pStruct->cropBottom;
    int cropTop = pStruct->cropTop;
    int resizeH = pStruct->resizeH;
    int resizeW = pStruct->resizeW;
    Rect cropRect = {};
    Dim resizeDim = {};
    if (cropRight <= 0 && cropLeft <= 0 && cropBottom <= 0 && cropTop <= 0) {}
    else
    {
        if ((cropRight - cropLeft) % 2 == 1 || (cropBottom - cropTop) % 2 == 1) return PARAM_CROP_ERR;
        cropRect.b = cropBottom;
        cropRect.t = cropTop;
        cropRect.l = cropLeft;
        cropRect.r = cropRight;
    }
    if (resizeH <= 0 && resizeW <= 0) {}
    else
    {
        if (resizeH % 2 == 1 || resizeW % 2 == 1) return PARAM_RESIZE_ERR;
        resizeDim.h = resizeH;
        resizeDim.w = resizeW;
    }
    int decRet = DEC_UNSUPPORT_FRAME;
    try
    {
        bool useDeviceFrame = false;
        CUdeviceptr pTmpImage = 0;
        std::vector<uint8_t>pImage;
        if (pStruct->rawDataType > 0)
        {
            useDeviceFrame = true;
        }

        decSetGlobals[gpuIdx].demuxer=new FFmpegDemuxer(fileIn, (double)pStruct->startTime * 0.001);
        decSetGlobals[gpuIdx].dec=new NvDecoder(decSetGlobals[gpuIdx].ctx, useDeviceFrame, FFmpeg2NvCodecId(decSetGlobals[gpuIdx].demuxer->GetVideoCodec()), false, false, &cropRect, &resizeDim);

        int nVideoBytes = 0, nFrameReturned = 0, nFrame = 0;
        uint8_t* pVideo = NULL, * pFrame;
        bool bDecodeOutSemiPlanar = false;
        int jump = pStruct->jump;
        MemBlock thisMemBlock(mem, pStruct->lmtMB, pStruct->memWithTimestamp);
        pStruct->retDecodeWidth = decSetGlobals[gpuIdx].demuxer->GetWidth();
        pStruct->retDecodeHeigth = decSetGlobals[gpuIdx].demuxer->GetHeight();
        pStruct->retFrameSize = decSetGlobals[gpuIdx].demuxer->GetFrameSize();
        if (!(pStruct->retDecodeWidth > 0 && pStruct->retDecodeHeigth > 0 && pStruct->retFrameSize > 0))
        {
            return DEC_HEIGHTWDITH_ERR;
        }
        if (pStruct->rawDataType > 0)
        {
            pStruct->retFrameSize *= 2;
            pImage.resize(pStruct->retFrameSize);
            cuMemAlloc(&pTmpImage, pStruct->retFrameSize);
        }
        int iMatrix = 0;
        unsigned long long totalInt = 0;
        std::cout << "\tjump = " << jump << std::endl;
        std::cout << "\tpklLmt = " << pStruct->lmtMB << std::endl;
        pStruct->retEndTime = -1;
        int64_t endTime = 0;
        TIMESTART;
        do {
            decSetGlobals[gpuIdx].demuxer->Demux(&pVideo, &nVideoBytes, &endTime);
            nFrameReturned = decSetGlobals[gpuIdx].dec->Decode(pVideo, nVideoBytes);
            if (decSetGlobals[gpuIdx].dec->GetOutputFormat() != cudaVideoSurfaceFormat_NV12 || nFrameReturned==0)
            {
                continue;
            }
            else
            {
                decRet &= OK;
            }
            if (endTime < pStruct->startTime)continue;
            //std::cout << endTime << std::endl;
            int pushRet = OK;
            for (int i = 0; i < nFrameReturned; i++) {
                if (decSetGlobals[gpuIdx].dec->GetBitDepth() != 8)
                {
                    return DEC_DEPTH_NOT_8;
                }
                pFrame = decSetGlobals[gpuIdx].dec->GetFrame();
                iMatrix = decSetGlobals[gpuIdx].dec->GetVideoFormatInfo().video_signal_description.matrix_coefficients;
                totalInt++;
                if (totalInt % jump == 0)
                {
                    switch (pStruct->rawDataType)
                    {
                    case 0:
                        pushRet = thisMemBlock.push(reinterpret_cast<char*>(pFrame), pStruct->retFrameSize);
                        break;
                    case 1:
                        Nv12ToColorPlanar<BGRA32>(pFrame, decSetGlobals[gpuIdx].dec->GetWidth(), (uint8_t*)pTmpImage, decSetGlobals[gpuIdx].dec->GetWidth(), decSetGlobals[gpuIdx].dec->GetWidth(), decSetGlobals[gpuIdx].dec->GetHeight(), iMatrix);
                        GetImage(pTmpImage, &pImage[0], decSetGlobals[gpuIdx].dec->GetWidth(), 3 * decSetGlobals[gpuIdx].dec->GetHeight());
                        pushRet = thisMemBlock.push(reinterpret_cast<char*>(&pImage[0]), pStruct->retFrameSize,endTime);
                        break;
                    case 2:
                        Nv12ToColorPlanar<RGBA32>(pFrame, decSetGlobals[gpuIdx].dec->GetWidth(), (uint8_t*)pTmpImage, decSetGlobals[gpuIdx].dec->GetWidth(), decSetGlobals[gpuIdx].dec->GetWidth(), decSetGlobals[gpuIdx].dec->GetHeight(), iMatrix);
                        GetImage(pTmpImage, &pImage[0], decSetGlobals[gpuIdx].dec->GetWidth(), 3 * decSetGlobals[gpuIdx].dec->GetHeight());
                        pushRet = thisMemBlock.push(reinterpret_cast<char*>(&pImage[0]), pStruct->retFrameSize,endTime);
                        break;
                    case 3:
                        Nv12ToColorPlanar_HWC<BGRA32>(pFrame, decSetGlobals[gpuIdx].dec->GetWidth(), (uint8_t*)pTmpImage, decSetGlobals[gpuIdx].dec->GetWidth(), decSetGlobals[gpuIdx].dec->GetWidth(), decSetGlobals[gpuIdx].dec->GetHeight(), iMatrix);
                        GetImage(pTmpImage, &pImage[0], decSetGlobals[gpuIdx].dec->GetWidth(), 3 * decSetGlobals[gpuIdx].dec->GetHeight());
                        pushRet = thisMemBlock.push((char*)&pImage[0], pStruct->retFrameSize,endTime);
                        break;
                    case 4:
                        Nv12ToColorPlanar_HWC<RGBA32>(pFrame, decSetGlobals[gpuIdx].dec->GetWidth(), (uint8_t*)pTmpImage, decSetGlobals[gpuIdx].dec->GetWidth(), decSetGlobals[gpuIdx].dec->GetWidth(), decSetGlobals[gpuIdx].dec->GetHeight(), iMatrix);
                        GetImage(pTmpImage, &pImage[0], decSetGlobals[gpuIdx].dec->GetWidth(), 3 * decSetGlobals[gpuIdx].dec->GetHeight());
                        pushRet = thisMemBlock.push(reinterpret_cast<char*>(&pImage[0]), pStruct->retFrameSize,endTime);
                        break;
                    default:return DEC_UNSUPPORT_RAWDATATYPE;
                    }
                    if (pushRet!=OK)
                    {
                        break;
                    }
                }
            }
            if (pushRet != OK)
            {
                pStruct->retEndTime = endTime;
                break;
            }
            nFrame += nFrameReturned;
        } while (nVideoBytes);
        pStruct->retMemFrameNum = thisMemBlock.frameCnt_;
        pStruct->retMemTimestampOffset= thisMemBlock.timestampOffset_;
        TIMEEND("decode loop : ");
        //std::vector <std::string> aszDecodeOutFormat = { "NV12", "P016", "YUV444", "YUV444P16" };
        //aszDecodeOutFormat[0] = "iyuv";   aszDecodeOutFormat[1] = "yuv420p16";
        //std::cout << "Total frame decoded: " << nFrame << std::endl << aszDecodeOutFormat[decSetGlobals[gpuIdx].dec->GetOutputFormat()] << " format" << std::endl;
    }
    catch (...)
    {
        return DEC_ANOTHER_ERR;
    }


    return decRet;

#endif
}




#if DLL<1
int main(int argc, char** argv)
{
    DecoderInit(0);
    char szInFilePath[256] = "D:/repo/Video_Codec_SDK_10.0.26/Samples/vs2019-nvcuvid-build/Release/mv.mp4";
    std::cout << "DecoderInit = " << DecoderInit(0) << std::endl;
    decoderParam param;
    memset((char*)&param,0,sizeof(param));
    param.jump = 1;
    param.lmtMB = 512;
    param.rawDataType = 0;
    param.startTime = 7300;
#if 0
    DecoderFile(0,szInFilePath, szInFilePath,&param);
#else
    std::vector<char> mem(param.lmtMB * 1024ll * 1024ll);
    DecoderMem(0,szInFilePath, &mem[0], &param);
#endif
    DecoderDestroy();
    std::cout << "[" << param.retDecodeHeigth << " , " << param.retDecodeWidth << "]" << std::endl;
    std::cout << "retMemFrameNum = " << param.retMemFrameNum   << std::endl;
    system("pause");


    return OK;
}
#endif
